package events;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;

import javax.management.InvalidAttributeValueException;

import org.apache.commons.math.random.RandomData;
import org.apache.commons.math.random.RandomDataImpl;

import util.Tuple;

import eduni.simjava.Sim_entity;
import eduni.simjava.Sim_event;
import eduni.simjava.Sim_port;
import eduni.simjava.Sim_stat;
import eduni.simjava.Sim_system;
import eduni.simjava.distributions.Sim_geometric_obj;
import eduni.simjava.distributions.Sim_negexp_obj;
import eduni.simjava.distributions.Sim_normal_obj;
import eduni.simjava.distributions.Sim_uniform_obj;

public class Client extends Sim_entity{
        
        //Download data
        private double fileSize;
        private String fileBeingRequested;
        private int currentFileIndex;
        private long numberOfBlocksToRequest;
        private int blockBeingRequested;
        
        private int blocksAlreadyDownloaded = 0;
        private int filesAlreadyDownloaded = 0;
        
        
        private String clientID;
        
        //Ports to link client to serverPool and LAN
        private Sim_port outToServerPool;
        private Sim_port inFromLan;

        //Object to collect statistics
        private Sim_stat stat;
        
        //Lists that contains data for our metrics
        private List<Tuple<Double, Integer>> blocksDownloadPerTime;
        private List<Tuple<Double, Integer>> filesDownloadPerTime;
        private HashMap<String, List<Integer>> blocksDownloadedPerFile;
        
        private double firstTimeDownloadingFile;
        private HashMap<String, List<Double>> downloadDurations;
        
        //      private final Sim_negexp_obj fileDistribution;
        private final HashMap<String, Double> fileDistribution;//Data of files that can be requested
        private final RandomData delay;//Distribution that will determine the time to be waited between files requests
        private final double delayMean;
        private Sim_uniform_obj fileToBeDownloadedDistribution;
        
//      public Client(String clientID, Sim_negexp_obj fileDistribution2, Sim_negexp_obj delay2){
        public Client(String clientID, HashMap<String, Double> filesSizes, double delayMean){
                super(clientID);
                
                this.clientID = clientID;
                this.fileDistribution = filesSizes;
                this.delay = new RandomDataImpl();
                this.delayMean = delayMean;
                
                fileToBeDownloadedDistribution = new Sim_uniform_obj("u", 0, filesSizes.size());
                double sample = fileToBeDownloadedDistribution.sample();
                
                currentFileIndex = determineIndexFromSample(sample); 
                
                this.fileBeingRequested = (String) this.fileDistribution.keySet().toArray()[currentFileIndex];
                this.fileSize = this.fileDistribution.get(fileBeingRequested);
                
                this.numberOfBlocksToRequest = (long) fileSize / RequestHeader.FILE_BLOCK_SIZE;
                blockBeingRequested = 0;
                firstTimeDownloadingFile = 0d;
                
                //Creating ports to communicate with the server and LAN
                outToServerPool = new Sim_port("outToServer");
                add_port(outToServerPool);
                
                inFromLan = new Sim_port("inFromLan");
                add_port(inFromLan);
                
                //Starting statistics of the client
                stat = new Sim_stat();
        stat.add_measure(Sim_stat.THROUGHPUT);
        stat.add_measure(Sim_stat.ARRIVAL_RATE);
        stat.add_measure(Sim_stat.QUEUE_LENGTH);
        stat.add_measure(Sim_stat.SERVICE_TIME);
        stat.add_measure(Sim_stat.UTILISATION);
        stat.add_measure(Sim_stat.WAITING_TIME);
        set_stat(stat);
        
        blocksDownloadPerTime = new ArrayList<Tuple<Double,Integer>>();
        filesDownloadPerTime = new ArrayList<Tuple<Double,Integer>>();
        downloadDurations = new HashMap<String, List<Double>>();
        blocksDownloadedPerFile = new HashMap<String, List<Integer>>();
        }
        
        private int determineIndexFromSample(double sample) {
                int intValue = (int) sample;
                
                if (intValue == 5){
                        return 4;
                }       
                
                return intValue;
        }

        public String getFileBeingDownloaded(){
                return fileBeingRequested;
        }
        
        public int getBlockBeingRequested(){
                return blockBeingRequested;
        }
        
        public void setFirstTimeDownloading(double firstTime){
                firstTimeDownloadingFile = firstTime;
        }
        
        public HashMap<String, List<Double>> getDurations(){
                return downloadDurations;
        }
        
        public List<Tuple<Double, Integer>> getBlocksPerTime(){
                return blocksDownloadPerTime;
        }
        
        public List<Tuple<Double, Integer>> getFilesPerTime(){
                return filesDownloadPerTime;
        }
        
        public void body(){
                while (Sim_system.running()) {

                        Sim_event e = new Sim_event();
                        // Getting the next event
                        sim_get_next(e);

                        if (e.get_tag() == EventType.LAN_CONFIRMATION) {
                                
                                //Updating server data in pool
                                ServerPool pool = ServerPool.getInstance();
                                try {
                                        pool.updateServerData(this.get_name(), fileBeingRequested, blockBeingRequested);
                                } catch (InvalidAttributeValueException e1) {
                                        e1.printStackTrace();
                                        continue;
                                }
                                
                                //Updating transfer information
                                blocksAlreadyDownloaded++;
                                blockBeingRequested++;
                                blocksDownloadPerTime.add(new Tuple(Sim_system.sim_clock(), blocksAlreadyDownloaded));
                                
                                // The event has completed service
                                sim_completed(e);
                                
                                if(blockBeingRequested >= numberOfBlocksToRequest){//Finished downloading a file
                                        filesAlreadyDownloaded++;
                                        filesDownloadPerTime.add(new Tuple(Sim_system.sim_clock(), filesAlreadyDownloaded));
                                        blockBeingRequested = 0;
                                        
                                        List<Double> fileData = downloadDurations.get(fileBeingRequested);
                                        if(fileData == null){
                                                fileData = new ArrayList<Double>();
                                                downloadDurations.put(fileBeingRequested, fileData);
                                                
                                        }
                                        fileData.add(Sim_system.clock() - firstTimeDownloadingFile);
                                        firstTimeDownloadingFile = Sim_system.clock();
                                        
                                        //Requesting a different file
                                        double sample = fileToBeDownloadedDistribution.sample();
                                        currentFileIndex = determineIndexFromSample(sample);

                                        this.fileBeingRequested = (String) this.fileDistribution.keySet().toArray()[currentFileIndex];
                                        this.fileSize = this.fileDistribution.get(fileBeingRequested);
                                        this.numberOfBlocksToRequest = (long) fileSize / RequestHeader.FILE_BLOCK_SIZE;
                                        
                                        //Waiting a time before requesting another file
                                        double interArrivalTime = this.delay.nextExponential(this.delayMean);
                                        
                                        sim_pause(interArrivalTime);
                                }       
                                
//                              System.out.println("TIME: "+Sim_system.clock()+" CLIENT "+this.get_name()+" REQ "+this.getBlockBeingRequested()+" "+this.getFileBeingDownloaded());
                                
                                //Sending transfer confirmation for client
                                sim_schedule(outToServerPool, 0.0, EventType.REQUEST_FILE, this);
                        }
                }
        }
        
        public int getFilesDownloaded(){
                return filesAlreadyDownloaded;
        }
        
        public int getBlocksDownloaded(){
                return blocksAlreadyDownloaded;
        }
        
        public long getAmountToDownload() {
                return RequestHeader.FILE_BLOCK_SIZE;
        }
}
